import re
from typing import Union

import django_filters
from django import conf
from django.core.validators import RegexValidator
from django.http import (
    FileResponse,
    Http404,
    HttpRequest,
    HttpResponse,
)
from knox.auth import TokenAuthentication
from main.models import (
    Assembly,
    Attachment,
    AttachmentGroups,
    Contract,
    Delivery,
    Reclamation,
    Request,
    Warranty,
    Worker,
)
from main.permissions import DjangoModelFullPermissions
from main.views.attachment_groups import AttachmentGroupsSerializer
from rest_framework import (
    permissions,
    request,
    response,
    serializers,
    status,
    viewsets,
)
from rest_framework.decorators import (
    api_view,
    authentication_classes,
    permission_classes,
)
from rest_framework.permissions import IsAuthenticated


class AttachmentWriteSerializer(serializers.ModelSerializer):
    request = serializers.PrimaryKeyRelatedField(
        queryset=Request.objects.all(), required=False
    )
    contract = serializers.PrimaryKeyRelatedField(
        queryset=Contract.objects.all(), required=False
    )
    warranty = serializers.PrimaryKeyRelatedField(
        queryset=Warranty.objects.all(), required=False
    )
    reclamation = serializers.PrimaryKeyRelatedField(
        queryset=Reclamation.objects.all(), required=False
    )
    delivery = serializers.PrimaryKeyRelatedField(
        queryset=Delivery.objects.all(), required=False
    )
    assembly = serializers.PrimaryKeyRelatedField(
        queryset=Assembly.objects.all(), required=False
    )
    worker = serializers.PrimaryKeyRelatedField(
        queryset=Worker.objects.all(), required=False
    )
    path = serializers.FileField(write_only=True, required=True)
    original_name = serializers.CharField(
        required=False,
        validators=(
            RegexValidator(
                regex=r"^[-\wа-яА-Я^&'@{}[\],$=!#().%+~ ]+\.\w+$",
                message="Некорректное имя файла.",
                code="invalid_original_name",
            ),
        ),
    )
    group = serializers.PrimaryKeyRelatedField(
        queryset=AttachmentGroups.objects.all(), required=False, allow_null=True
    )

    class Meta:
        model = Attachment
        fields = (
            "path",
            "original_name",
            "group",
            "request",
            "contract",
            "warranty",
            "reclamation",
            "delivery",
            "assembly",
            "worker",
        )

    def validate(self, data):
        fields = (
            "request",
            "contract",
            "warranty",
            "reclamation",
            "delivery",
            "assembly",
            "worker",
        )
        if not any(data.get(field) for field in fields):
            raise serializers.ValidationError(
                "At least one of the following fields must be set: "
                + ", ".join(fields)
            )
        if "original_name" in data and not re.fullmatch(
            r"^[a-zA-Zа-яА-Я0-9_\s]+\.\w+$", data["original_name"]
        ):
            raise serializers.ValidationError("Not valid original_name.")
        return data


class AttachmentUpdateSerializer(serializers.ModelSerializer):
    request = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    contract = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    warranty = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    reclamation = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    delivery = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    assembly = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    worker = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    group = serializers.PrimaryKeyRelatedField(
        queryset=AttachmentGroups.objects.all(), required=False, allow_null=True
    )

    class Meta:
        model = Attachment
        fields = (
            "id",
            "original_name",
            "group",
            "request",
            "contract",
            "warranty",
            "reclamation",
            "delivery",
            "assembly",
            "worker",
        )


class AttachmentSerializer(serializers.ModelSerializer):
    request = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    contract = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    warranty = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    reclamation = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    delivery = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    assembly = serializers.PrimaryKeyRelatedField(
        read_only=True, required=False
    )
    worker = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    url = serializers.URLField(
        source="get_attach_url", read_only=True, required=False
    )
    creator = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    size = serializers.IntegerField(read_only=True, required=False)
    group = AttachmentGroupsSerializer(required=False)

    class Meta:
        model = Attachment
        fields = (
            "id",
            "url",
            "original_name",
            "content_type",
            "size",
            "creator",
            "created_on",
            "updated_on",
            "group",
            "request",
            "contract",
            "warranty",
            "reclamation",
            "delivery",
            "assembly",
            "worker",
        )


class AttachmentFilter(django_filters.FilterSet):
    class Meta:
        model = Attachment
        fields = (
            "request",
            "contract",
            "warranty",
            "reclamation",
            "delivery",
            "assembly",
            "worker",
        )


class AttachmentPermissions(permissions.BasePermission):
    @staticmethod
    def _get_object_relation(
        http_request: request.Request,
    ) -> str | None:
        for field in (
            "request",
            "contract",
            "warranty",
            "reclamation",
            "delivery",
            "assembly",
            "worker",
        ):
            if field in http_request.query_params or field in http_request.data:
                return field
        return None

    def has_permission(self, http_request: request.Request, view):
        relation_field = self._get_object_relation(http_request)
        if relation_field is None:
            return False

        action = {
            "GET": "view",
            "POST": "add",
            "PUT": "change",
            "PATCH": "change",
            "DELETE": "delete",
        }[http_request.method]

        return http_request.user.has_perm(f"main.{action}_{relation_field}")


class AttachmentViewSet(viewsets.ModelViewSet):
    queryset = Attachment.objects.all().order_by("created_on")
    serializer_class = AttachmentSerializer
    permission_classes = (
        DjangoModelFullPermissions,
        AttachmentPermissions,
    )
    filter_backends = [
        django_filters.rest_framework.DjangoFilterBackend,
    ]
    filterset_class = AttachmentFilter

    def update(self, http_request, *args, **kwargs):
        partial = kwargs.pop("partial", False)
        instance = self.get_object()
        serializer = AttachmentUpdateSerializer(
            instance, data=http_request.data, partial=partial
        )
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        return response.Response(AttachmentSerializer(instance).data)

    def create(self, http_request, *args, **kwargs):
        serializer = AttachmentWriteSerializer(data=http_request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)

        return response.Response(
            AttachmentSerializer(serializer.instance).data,
            status=status.HTTP_201_CREATED,
            headers=headers,
        )

    def perform_create(self, serializer):
        serializer.save(
            creator=self.request.user,
            size=serializer.validated_data["path"].size,
            original_name=serializer.validated_data["original_name"]
            if "original_name" in serializer.validated_data
            else serializer.validated_data["path"].name,
            content_type=serializer.validated_data["path"].content_type,
        )


@api_view(["GET"])
@authentication_classes([TokenAuthentication])
@permission_classes([IsAuthenticated])
def view_attach(
    http_request: HttpRequest, pk: int
) -> HttpResponse | FileResponse:
    try:
        attach = Attachment.objects.get(id=pk)
    except Request.DoesNotExist as err:
        raise Http404 from err

    http_response: Union[FileResponse, HttpResponse]
    if conf.settings.DEBUG:
        http_response = FileResponse(attach.path.file)
    else:
        # In prod configure NGINX
        # https://django-sendfile2.readthedocs.io/en/latest/backends.html#nginx-backend
        http_response = HttpResponse()
        # Content-type will be detected by nginx
        del http_response["Content-Type"]
        http_response["X-Accel-Redirect"] = attach.path.file

    if "download" in http_request.GET:
        http_response["Content-Disposition"] = (
            f'attachment; filename="{attach.original_name}"'
        )

    return http_response
